package org.argeproje.resim.proc.tools;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;

import org.argeproje.resim.proc.ConnectionRule;
import org.argeproje.resim.proc.Processor;
import org.argeproje.resim.proc.data.Data;




public class ProcessorGraph{
	public final static int RET_OK = 0;
	public final static int RET_PROCESSOR_EXIST = 1;
	public final static int RET_INVALID_CONNECTION = 2;
	public final static int RET_CONNECTION_EXIST = 3;
	public final static int RET_PROCESSOR_NOT_EXIST = 4;
	public final static int RET_SRC_PROCESSOR_NULL = 5;
	public final static int RET_DST_PROCESSOR_NULL = 6;
	public final static int RET_SRC_INDEX_INVALID = 7;
	public final static int RET_DST_INDEX_INVALID = 8;
	private static ProcessorGraph _instance;
	protected HashMap<Integer, ProcessorNode> _procNodeMap;
	protected ArrayList<ArrayList<ProcessorNode>> _nodeLevelMap;
	private ProcessorGraph(){
		_procNodeMap = new HashMap<Integer, ProcessorNode>();
		_nodeLevelMap = new ArrayList<ArrayList<ProcessorNode>>();
	}
	
	public static ProcessorGraph getInstance(){
		if(_instance == null){
			_instance = new ProcessorGraph();
		}
		return _instance;
	}
	
	protected HashMap<Integer, ProcessorNode> getProcNodeMap(){
		return _procNodeMap;
	}
	

	
	public int newConnection(Processor srcProc, int srcOutIndex,
			Processor dstProc, int dstInIndex) {
		int retCode = ProcessorGraph.RET_OK;
		if (srcOutIndex < 0) {
			retCode = ProcessorGraph.RET_SRC_INDEX_INVALID;
		}
		if (dstInIndex < 0) {
			retCode = ProcessorGraph.RET_DST_INDEX_INVALID;
		}
		if (srcProc == null) {
			retCode = ProcessorGraph.RET_SRC_PROCESSOR_NULL;
		}
		if (dstProc == null) {
			retCode = ProcessorGraph.RET_DST_PROCESSOR_NULL;
		}
		if (retCode == ProcessorGraph.RET_OK) {
			if (!nodeExists(srcProc)) {
				this.addProcessor(srcProc);
			}
			if (!nodeExists(dstProc)) {
				this.addProcessor(dstProc);
			}
			if (nodeExists(srcProc) && nodeExists(dstProc)) {
				if (validConnection(srcProc, srcOutIndex, dstProc, dstInIndex)) {
					ProcessorNode srcNode = getProcNode(srcProc.getId());
					ProcessorNode dstNode = getProcNode(dstProc.getId());
					if (srcNode.isOutConnectionExist(srcOutIndex, dstInIndex,
							dstNode)) {
						retCode = ProcessorGraph.RET_CONNECTION_EXIST;
					} else {
						srcNode.newOutConnection(srcOutIndex, dstInIndex,
								dstNode);
					}
				} else {
					retCode = ProcessorGraph.RET_INVALID_CONNECTION;
				}
			} else {
				retCode = ProcessorGraph.RET_PROCESSOR_NOT_EXIST;
			}
		}
		printError(retCode);
		return retCode;
	}
	public int deleteConnection(Processor srcProc, int srcOutIndex,
			Processor dstProc, int dstInIndex){
		int retCode = ProcessorGraph.RET_OK;
		if (nodeExists(srcProc) && nodeExists(dstProc)) {
				ProcessorNode srcNode = getProcNode(srcProc.getId());
				srcNode.deleteOutConnection(srcOutIndex, dstProc, dstInIndex);
				
		} else {
			retCode = ProcessorGraph.RET_PROCESSOR_NOT_EXIST;
		}
		return retCode;
	}
	public boolean validConnection(Processor src, int srcOutIndex,
			Processor dst, int dstInIndex){
		return ConnectionRule.validConnection(src.getConnectionRule(), srcOutIndex, 
				dst.getConnectionRule(), dstInIndex);
	}
	public int addProcessor(Processor pr){
		int retCode = ProcessorGraph.RET_OK;
		if(!nodeExists(pr)){
			ProcessorNode node = new ProcessorNode(pr);
			getProcNodeMap().put(new Integer(pr.getId()), node);
		}else{
			retCode = ProcessorGraph.RET_PROCESSOR_EXIST;
		}
		return retCode;
	}
	public int removeProcessor(Processor pr){
		int retCode = ProcessorGraph.RET_OK;
		if(nodeExists(pr)){
			ProcessorNode node = getProcNodeMap().get(new Integer(pr.getId()));
			node.removeConnections();
			getProcNodeMap().remove(new Integer(pr.getId()));
		}else{
			retCode = ProcessorGraph.RET_PROCESSOR_NOT_EXIST;
		}
		return retCode;		
	}
	protected boolean nodeExists(Processor pr){
		boolean result = getProcNodeMap().containsKey(new Integer(pr.getId()));
		return result;
	}
	protected ProcessorNode getProcNode(int id){
		return getProcNodeMap().get(new Integer(id));
	}
	private boolean createFirstLevelNodes(){
		_nodeLevelMap.clear();
		Collection<ProcessorNode> nodes = getProcNodeMap().values();
		Iterator<ProcessorNode> it = nodes.iterator();
		ArrayList<ProcessorNode> levelNodes = new ArrayList<ProcessorNode>();
		while(it.hasNext()){
			ProcessorNode node = it.next();
			if(node.connected() && node.getNoInputs() == 0){
				levelNodes.add(node);
			}
		}
		boolean result = true;
		if(levelNodes.isEmpty()){
			result = false;
		}else{
			_nodeLevelMap.add(levelNodes);
		}
		return result;
	}
	private boolean createNextLevelNodes(){
		int level = _nodeLevelMap.size();
		if(level == 0){
			return createFirstLevelNodes();
		}
		ArrayList<ProcessorNode> preLevelNodes = _nodeLevelMap.get(level - 1);
		Iterator<ProcessorNode> it = preLevelNodes.iterator();
		ArrayList<ProcessorNode> levelNodes = new ArrayList<ProcessorNode>();
		while(it.hasNext()){
			ProcessorNode node = it.next();
			int noOutput = node.getNoOutputs();
            for(int i = 0; i < noOutput; i++){
            	ArrayList<Connection> outConns = node.getOutConnections(i);
            	for (Iterator<Connection> iterator = outConns.iterator(); iterator
						.hasNext();) {
					Connection conn = (Connection) iterator.next();
					if(conn.connected()){
					  ProcessorNode nextNode = conn.getToNode();
					  //Not contained already in this level
					  if(!levelNodes.contains(nextNode)){
						  //Input connections are not empty
						  if(nextNode.connected()){
							  //Each input is contained in a lower level
							  boolean validNode = true;
							  for(int k = 0; k < nextNode.getNoInputs(); k++){
								  Connection inConn = nextNode.getInConnection(k);
								  validNode &= containedInLevelNodes(inConn.getFromNode());
							  }
							  if(validNode){
								  levelNodes.add(nextNode);
							  }
						  }
					  }
					}
				}
            }
		}
		boolean result = true;
		if(levelNodes.isEmpty()){
			result = false;
		}else{
			_nodeLevelMap.add(levelNodes);
		}
		return result;
	}
	private void createNodeLevels(){
		createFirstLevelNodes();
		while(createNextLevelNodes()){
			;
		}
	}
	protected boolean containedInLevelNodes(ProcessorNode node){
		boolean result = false;
		for (Iterator<ArrayList<ProcessorNode>> it = _nodeLevelMap.iterator(); it.hasNext();) {
			ArrayList<ProcessorNode> levelNodes = (ArrayList<ProcessorNode>) it.next();
			if(levelNodes.contains(node)){
				result = true;
				break;
			}
			
		}
		return result;
	}
	private ArrayList<ProcessorNode> getNodeLevel(int index){
		ArrayList<ProcessorNode> level = null;
		if(index < _nodeLevelMap.size()){
			level = _nodeLevelMap.get(index);
		}
		return level;
	}
	public void clearGraph(){
		_procNodeMap.clear();
	}
	public void run(){
		createNodeLevels();
		int level = 0;
		ArrayList<ProcessorNode> levelNodes = null;
		while(getNodeLevel(level) != null){
			levelNodes = getNodeLevel(level);
			for (Iterator<ProcessorNode> iterator = levelNodes.iterator(); iterator.hasNext();) {
				ProcessorNode processorNode = (ProcessorNode) iterator.next();

				for(int i = 0; i < processorNode.getNoInputs(); i++){
						Connection conn = processorNode.getInConnection(i);
						Data inputData = conn.getFromNode().getProcessor().getOutput(conn.getFromIndex());
						processorNode.getProcessor().setInput(inputData, i);
					}
				processorNode.getProcessor().process();	
				
			}
			level++;
		}
	}
	public  void printError(int retCode){
		if(retCode == ProcessorGraph.RET_OK){
			return;
		}
		System.out.println("ProcessorGraph.printError->");
		switch (retCode) {
		case ProcessorGraph.RET_INVALID_CONNECTION:
			System.out.println("INVALID CONNECTION");
			break;
		case ProcessorGraph.RET_PROCESSOR_EXIST:
			System.out.println("PROCESSOR EXISTS");
			break;
		case ProcessorGraph.RET_PROCESSOR_NOT_EXIST:
			System.out.println("PROCESSOR NOT EXIST");
			break;
		case ProcessorGraph.RET_CONNECTION_EXIST:
			System.out.println("CONNECTION EXIST");
			break;
		case ProcessorGraph.RET_SRC_PROCESSOR_NULL:
			System.out.println("SRC PROCESSOR NULL");
			break;
		case ProcessorGraph.RET_DST_PROCESSOR_NULL:
			System.out.println("DST PROCESSOR NULL");
			break;
		case ProcessorGraph.RET_SRC_INDEX_INVALID:
			System.out.println("SRC INDEX INVALID");
			break;
		case ProcessorGraph.RET_DST_INDEX_INVALID:
			System.out.println("DST INDEX INVALID");
			break;
		default:
			break;
		}
	}
}
